Modules Should Be Deep
「モジュールは深くあるべき」Deep Module
https://scrapbox.io/files/6580e935fabfbd0025c63e76.png
狭く深く <-> 広く浅く
モジュール設計の大切さ
システムの複雑さ = 最も複雑なモジュールの複雑さ
複雑さを軽減するには、モジュール設計が重要となるradish-miyazaki.icon
理想論で言えば、各モジュールは他のモジュールから完全に独立している
結合度 - メッセージ結合
他のモジュールについて知らなくても、どのモジュールでも作業できる
これはあくまで理想
実際問題、モジュールは互いのクラスやメソッドを呼び出す必要がある
結果として、互いのモジュールを知っている必要がある
これにより、あるモジュールが変更されると、他のモジュールも変更される
依存関係が生まれる
モジュール設計の目標は、モジュール間の依存関係を最小限にすることである。
依存関係を管理するには、各モジュールをインタフェースと実装に分離することが必要
良いモジュール = インタフェースが実装よりもはるかにシンプル
これにより、以下の2つの恩恵を受けられる
シンプルなインタフェースは、モジュールがシステムの他の部分に与える複雑さを最小限にする
あるモジュールを(インタフェースを変更しないで)変更した場合、その変更は他のモジュールに影響を与えない
モジュールのインタフェースは2つの情報を持つ
形式的な情報( formal information)
プログラミング言語によって正しさがチェックされる部分
e.g. パラメータ名や型、戻り値の型、例外
非形式的な情報( unformal information )
プログラミング言語が理解したり、強制したりすることができない部分
e.g. 使い方に制約があるメソッド
あるメソッドが別のメソッドより先に呼び出す必要があるなど
この部分はコメントでのみ記述することができる
プログラミング言語はこの記述が正確であるかを判断できない
Comments Should Describe Things that Ain't Obvious from the Code
formal information よりも大きく複雑
関連するモジュールを使用するために何を知る必要があるかを正確に示すことができる
未知の未知 の解消に役立つ
各モジュールは、インタフェースという抽象化を提供する
インタフェースは、モジュールの機能を簡略化したもの
実装の詳細は抽象化の観点から重要ではないので省かれている
抽象化の定義において、重要ではない という言葉は極めて重要
抽象化から詳細を省略できるのは、本当に重要でない詳細を含んでいないときだけ
省かれる重要ではない詳細は、多ければ多いほど良い
抽象化のよくある2つの間違い
重要でない詳細を含んでしまう
抽象化が複雑になり、抽象化を用いる開発者の認知的負荷が増大する
重要な詳細を含んでいない
Modules Should Be Deep#6581245d75d04f0000ccd5af
抽象化を用いる開発者が、正しく使うために必要な情報を取得できない
未知の未知
これを 偽りの抽象化(false abstraction) と呼ぶ
抽象化を設計する上で重要なのは、何が重要なのかを理解し、重要な情報量を最小限に抑える設計を探すことである
e.g. ファイルシステム、電子レンジ、車
良いモジュール = 強力な機能を提供しながらもシンプルなインタフェースを持つ
このようなモジュールを、深いモジュール(Deep Module)と呼ぶ
https://scrapbox.io/files/6580e935fabfbd0025c63e76.png
シンプルなインタフェースの後ろに多くの機能が隠されている。
内部の複雑さの一部しかユーザに見せない
良い抽象化
深いモジュールはコスパ最高radish-miyazaki.icon
費用対効果
モジュールにおける効果: 機能
モジュールにおける費用: インタフェース
インタフェース = 他の部分に課す複雑さ
Modules Should Be Deep#6580e5ff75d04f00009f0d46
インタフェースが小さくて単純であれば、もたらされる複雑さを軽減できる
e.g. Unix / Linux が提供する File I/O
open,read, write, lseek, close の5つのみ
e.g. Go や Java などの Garbage Collection
深いモジュールの逆は、浅いモジュール(Shallow Module)
提供する機能と比べて、インタフェースが複雑なモジュール
e.g. 連結リスト を実装するクラス
操作するのに必要なコードはそれほど多くないので、抽象化は詳細をあまり隠さない
インタフェースの複雑さ = 実装の複雑さ
code:java
private void addNullValueForAttribute(String attribute) {
data.put(attribute, null);
}
インタフェースを導入すると複雑さが増し、それによるメリットも少ない
コスパが悪い
避けられない場合もあるが、複雑さを管理する上では役立たない
レッドフラグ
Classitis
Deep Class の価値はあまり理解されていない
クラスは小さくあるべきという考え方が根付いている
N 行より長いメソッドは、複数のメソッドに分割すべき
大量の Shallow Class や Shallow Method が発生し、複雑さが増す
Martin Fowler の リファクタリング と真逆のことを言ってるradish-miyazaki.icon
大きすぎるクラスもクラスで問題なので、真逆では無いradish-miyazaki.icon
単一責任の原則
Classitis に陥ったシステムは、個々のクラスはシンプルになるが、システム全体の複雑さが増す
小さなクラスはあまり機能に貢献しない
機能を実現するために大量のクラスが必要になる
それぞれが独自インタフェースを持つ
ボイラーテンプレートのために、冗長な記述になる
Java のコミュニティで特に顕著
インタフェースは、一般的なケースをできるだけシンプルにするように実装されるべき
Java の ファイル I/O は違反している例
ほとんどのユーザはバッファリングを必要とするので、デフォルトで提供されるべき
code:java
fileInputStream fileStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);
ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);
Information Hiding の観点から見ても良くない
よく使われる機能のための API が、滅多に使われない他の機能についてユーザに学習を強いることで、認知的負荷 が高くなる
上記の例では、滅多に使われない他の機能 = バッファリングが必要ないケース
Overexposure - レッドフラグ
部分的に Information Hiding を適用することも可能
Information Hiding (and Leakage)#6581477075d04f000030ab80
hr.icon
要約
モジュールをインタフェースと実装に分離することで、実装の複雑さを他の部分から隠蔽できる
Information Hiding
ユーザはインタフェースによって提供される抽象化だけを理解すれば良い
モジュール設計で大事なのは、Deep Module を実現すること
Deep Module
一般的なユースケースを実現するためのシンプルなインタフェースを提供する
重要な機能を提供する
これにより、最大限まで複雑さを隠蔽できる
#読書メモ